package com.ncs.nc.service.impl;

import com.ncs.nc.entity.Constant;
import com.ncs.nc.entity.Goods;
import com.ncs.nc.mapper.GoodsMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author LEI
 */
@Service
public class RedisServiceImpl implements CommandLineRunner {


    private static final Logger log= LoggerFactory.getLogger(RedisServiceImpl.class);

    @Resource
    private GoodsMapper goodsMapper;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 实现commandLineRunner接口，项目启动完毕就将相应得商品数据设置进缓存，并设置ttl
     * 目的：当ttl快到时，模拟发起多个线程并发访问”获取缓存中得数据“接口
     * @param args
     * @throws Exception
     */
    @Override
    public void run(String... args) throws Exception {
        Goods goods=goodsMapper.selectById(Constant.RedisJiChuanId);
        if(goods!=null){
            //设置该Key在缓存中得存活时间ttl为5秒钟
            redisTemplate.opsForValue().set(Constant.RedisJiChuanId,goods,3L, TimeUnit.SECONDS);
            log.info("~项目启动完毕，成功将数据库中的数据设置进缓存中~");
        }
    }

    /**
     * 缓存击穿--正常流程
     */
    public Goods getByIdV1(final Integer id){
        Goods goods;
        ValueOperations<String,Goods> operations=redisTemplate.opsForValue();
        if (redisTemplate.hasKey(Constant.RedisJiChuanKey)){
            log.info("~缓存中存在该数据，直接取出~");
            return operations.get(Constant.RedisJiChuanKey);
        }else{
            log.info("~缓存中不存在该数据，从数据库中取出~");
            goods=goodsMapper.selectById(id);
            if (goods!=null){
                operations.set(Constant.RedisJiChuanKey,goods,6L,TimeUnit.SECONDS);
            }
        }
        return goods;
    }

    /**
     * 缓存击穿-分布式锁解决方案
     */
    public Goods getByIdV2(final Integer id){
        Goods goods=null;
        ValueOperations<String,Goods> operations=redisTemplate.opsForValue();
        if (redisTemplate.hasKey(Constant.RedisJiChuanKey)){
            log.info("~分布式锁，缓存中存在该数据，直接取出~");
            return operations.get(Constant.RedisJiChuanKey);
        }else{
            //加分布式锁--其实是利用Redis底层操作API的原子性，
            //由setNX以及expire等API组成实现一分布式锁
            //其中共享资源 /临界资源为id
            final String key=Constant.RedisJiChuanLock+id;
            String value= UUID.randomUUID().toString().replaceAll("-","")+System.nanoTime();
            ValueOperations<String,String> opera=redisTemplate.opsForValue();

            try {
                //setIfAbsent: 如果执行完该行代码后返回true, 则达标缓存中该Key不存在
                //当前线程获取到了共享资源的互斥锁；之所以可以这样，因为setIfAbsent是一个原子性操作
                //即同一时间并发产生的多个线程只允许由一个线程可以成功执行该方法
                Boolean res=opera.setIfAbsent(key,value);
                if (res){
                    redisTemplate.expire(key,3L,TimeUnit.SECONDS);

                    //真正的业务处理逻辑
                    if(redisTemplate.hasKey(Constant.RedisJiChuanKey)){
                        return operations.get(Constant.RedisJiChuanKey);
                    }else{
                        log.info("~分布式锁-缓存中不存在该数据，从数据库中取出~");
                        goods=goodsMapper.selectById(id);
                        if (goods!=null){
                            operations.set(Constant.RedisJiChuanKey,goods,10L,TimeUnit.SECONDS);
                        }
                    }
                }
            }finally {
                String tmpValue=opera.get(key);
                if(StringUtils.isNotBlank(tmpValue)&&tmpValue.equals(value)){
                    redisTemplate.delete(key);
                }
            }
        }
        return goods;
    }

    /**
     * 缓存穿透--解决方案
     */
    public Goods getByIdV3(final Integer id){
        //最基本的判断
        if(id==null||id<0){
            return null;
        }
        Goods goods;
        ValueOperations<String,Goods> operations=redisTemplate.opsForValue();
        if (redisTemplate.hasKey(Constant.RedisChuanTouKey)){
            log.info("~缓存中存在该数据，直接取出~");
            return operations.get(Constant.RedisChuanTouKey);
        }else{
            log.info("~缓存中不存在该数据，从数据库中取出~");
            goods=goodsMapper.selectById(id);

            //缓存和数据库中都查不到数据时，意味着goods=null;
            //此时需要将null值设置回缓存中
            if (goods==null){
                operations.set(Constant.RedisChuanTouKey,null,6L,TimeUnit.SECONDS);
            }else{
                operations.set(Constant.RedisChuanTouKey,goods,6L, TimeUnit.SECONDS);
            }
        }
        return goods;
    }
}
